package com.yuand.utils;

import com.yuand.constant.Constant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author yuandong
 * @date 2021/8/20 10:39
 */
@Slf4j
public class SignUtils {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 校验Header上的参数-验证是否传入值
     * <p>
     * 有个很重要的一点，就是对此请求进行时间验证，如果大于10分钟表示此链接已经超时，防止别人来到这个链接去请求。这个就是防止盗链。
     *
     * @param request
     * @return
     */
    public static boolean verifyHeaderParams(HttpServletRequest request) {
        //应用id
        String appKey = request.getHeader(Constant.APP_KEY);
        if (StringUtils.isEmpty(appKey)) {
            return false;
        }

        //时间戳,增加链接的有效时间,超过阈值,即失效
        String timeStamp = request.getHeader(Constant.TIME_STAMP);
        if (StringUtils.isEmpty(timeStamp)) {
            return false;
        }

        //调用方传递的签名
        String signature = request.getHeader(Constant.SIGN);
        if (StringUtils.isEmpty(signature)) {
            return false;
        }

        // 临时流水号/随机串 ,至少为10位 ，有效期内防重复提交
        String nonce = request.getHeader(Constant.NONCE);
        if (StringUtils.isEmpty(nonce)) {
            return false;
        }

        //毫秒  暂时不验证该参数
           /* long diff = System.currentTimeMillis() - Long.parseLong(timeStamp);
            //大于10分钟
            if (diff > 1000 * 60 * 10) {
                return false;
            }*/

        return true;
    }


    //生成签名
    public static String signature(String requestURI, LinkedHashMap<String, List<String>> parameters, String nonce, long timestamp, String requestBody, String appKey, String appSecret) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
        //参数进行自然排序
        parameters = parametersRank(parameters);
        StringBuilder builder = new StringBuilder(requestURI);
        for (Map.Entry<String, List<String>> paramEntry : parameters.entrySet()) {
            for (String value : paramEntry.getValue()) {
                builder.append(builder.indexOf("?") == -1 ? "?" : "&")
                        .append(paramEntry.getKey())
                        .append("=")
                        .append(value);
            }
        }
        String url = builder.toString();
        String signInfo = String.format("%sappKey=%s&nonce=%s&timestamp=%s%s", String.format("%s%s", url, url.contains("?") ? "&" : "?"), appKey, nonce, timestamp, requestBody);
        SecretKey secretKey = new SecretKeySpec(appSecret.getBytes(Constant.UTF8), Constant.SIGNATURE_ALGORITHM);
        Mac mac = Mac.getInstance(Constant.SIGNATURE_ALGORITHM);
        mac.init(secretKey);
        byte[] bytes = Base64.encodeBase64(mac.doFinal(signInfo.getBytes(Constant.UTF8)));
        String sign = new String(bytes, Constant.UTF8);
        log.info("signature completed ...... url [{}] payload [{}] sign [{}]", url, signInfo, sign);
        return sign;
    }


    //将url中携带的参数进行自然排序
    private static LinkedHashMap<String, List<String>> parametersRank(LinkedHashMap<String, List<String>> parameters) {
        LinkedHashMap<String, List<String>> rankedParameters = new LinkedHashMap<>(parameters.size());
        if (parameters.size() != 0) {
            List<String> keys = new ArrayList<>(parameters.keySet());
            Collections.sort(keys);     //对所有key值进行自然排序
            for (String key : keys) {
                List<String> values = parameters.get(key);
                values.sort(Comparator.naturalOrder());  //Comparator.naturalOrder()返回的是一个比较器，可以实现自然排序；和上面(103行)的排序是一样的
                rankedParameters.put(key, values);
            }
        }
        return rankedParameters;
    }


    /**
     * 比较签名是否正确
     */
    public static boolean verifySignature(SortedMap<String, String> params, String sign_houtai) {
        //调用方传过来的签名
        String paramSignature = params.get(Constant.SIGN);
        log.info("调用方传过来的Sign:{}", paramSignature);
        if (params == null || StringUtils.isEmpty(sign_houtai)) {
            return false;
        }

        //比较调用方传的签名 与 后台生成的签名
        return StringUtils.equals(paramSignature, sign_houtai);
    }


    /**
     * 上面的流程中，会有个额外的安全处理，防止盗链，我们可以让链接有失效时间
     * 而利用nonce参数，可以防止重复提交,在签名验证成功后，判断是否重复提交,原理就是结合redis，判断是否已经提交过
     *
     * @param appKey
     * @param timeStamp 13位时间戳
     * @param nonce     临时流水号/随机串 ,至少为10位 ，有效期内防重复提交
     * @param signature 接口签名
     * @return 是否重复请求
     */
    public boolean isReplayAttack(String appKey, String timeStamp, String nonce, String signature) {
        StringBuilder redisKey = new StringBuilder();
        redisKey.append("IS_REPLAY_ATTACK").append(":")
                .append(Constant.APP_KEY).append(":").append(appKey)
                .append(Constant.TIME_STAMP).append(":").append(timeStamp)
                .append(Constant.NONCE).append(":").append(nonce)
                .append(Constant.SIGN).append(":").append(signature);

        Object value = redisTemplate.opsForValue().get(redisKey);

        if (value != null && StringUtils.equals(signature, value.toString()))
            return false;
        else
            redisTemplate.opsForValue().set(redisKey, signature, 1000 * 50);
        return false;
    }

}
